package wangjg.coco.common;

import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;

import wangjg.commons.bean.BeanUtil;
import wangjg.mda.context.SourceContext;
import wangjg.mda.lang.CodeTX;
import wangjg.mda.lang.MyComment;
import wangjg.mda.lang.MyDocParameter;
import wangjg.mda.model.obj.Method;
import wangjg.mda.model.obj.Model;
import wangjg.mda.model.obj.ModelField;
import wangjg.mda.model.obj.Parameter;

public class AstModelContext extends SourceContext {
	
	public static void main(String args[]){
		String filePath = "E:/workspace/scaffold/springmvc/src/main/java/com/scaffold/authority/entity/UserAuthorityRelation.java";
//		String filePath = "E:/gewaworkspace/gptbs/branches/develop-branches/src/main/java/com/gewara/gptbs/model/notice/NoticeReadUser.java";
//		String filePath = "E:/gewaworkspace/gptbs/branches/develop-branches/src/main/java/com/gewara/gptbs/web/action/api/sale/TicketSaleAction.java";
		AstModelContext ctx = new AstModelContext();
		ctx.setCharset("gbk");
		Model model = ctx.loadModel(new File(filePath));
		System.out.println(model);
	}

	public Model loadModel(File file){
		AstModelParser cp = new AstModelParser();
		cp.loadSource(file, this.getCharset());
		Model model = cp.loadModel();
		return model;
	}
	
}

class AstModelParser extends CodeTX{
	private CompilationUnit cunit = null;
	
	public void loadSource(File file, String charset){
		this.loadSource(file.getPath(), charset);
		ASTParser astParser = ASTParser.newParser(AST.JLS3);
		astParser.setKind(ASTParser.K_COMPILATION_UNIT);
		char[] cbuf = this.getSourceCode().toCharArray();
		astParser.setSource(cbuf);
		cunit = (CompilationUnit) astParser.createAST(null);
	}
	
	public Model loadModel(){
		Model model = this.getModel(cunit);
		return model;
	}

	private Model getModel(CompilationUnit cunit){
		PackageDeclaration jpackage = cunit.getPackage();
		TypeDeclaration type = getPublicType(cunit);
		Type superType = type.getSuperclassType();

		String packageName = jpackage.getName().getFullyQualifiedName();
		String modelName = type.getName().getFullyQualifiedName();
		
		Model model = new Model();
		model.setPackage(packageName);
		model.setName(modelName);
		model.setParent(this.getFullName(cunit, (SimpleType)superType));
		this.setModelAnno(model, type);
		this.setModelComment(model, type);
		
		this.setModelMembers(model, type);
		
		this.handleModelFullName(model);
		this.handleManyToMany(model);
		
		return model;
	}

	private void setModelComment(Model model, TypeDeclaration type) {
		int pos1 = type.getStartPosition();
		int pos2 = type.getName().getStartPosition() + type.getName().getLength();
		MyComment comment = this.queryComment2(pos1, pos2);
		if(comment != null){
			String label = comment.getValue();
			model.setLabel(label);
		}
		
		//
		model.setAttributes(this.getAttributes());
	}

	private void setModelAnno(Model model, TypeDeclaration type) {
		MyVisitor visitor = new MyVisitor();
		type.accept(visitor);
		int typeAnnoEndPos = type.getName().getStartPosition();
		List<Annotation> typeAnnoList = visitor.get(type.getStartPosition(), typeAnnoEndPos);
	}

	private TypeDeclaration getPublicType(CompilationUnit cunit){
		List types = cunit.types();
		for(Object obj:types){
			if(obj instanceof TypeDeclaration){
				TypeDeclaration type = (TypeDeclaration)obj;
				if((type.getModifiers()&1)==1){
					return type;
				}
			}
		}
		
		return null;
	}
	
	private void setModelMembers(Model model, TypeDeclaration type) {
		List<ModelField> fieldList = new ArrayList<ModelField>();
		List<ModelField> staticFieldList = new ArrayList<ModelField>();
		List<Method> methodList = new ArrayList<Method>();
		model.setFields(fieldList);
		model.setMethods(methodList);
		model.setStaticFields(staticFieldList);
		
		List<BodyDeclaration> bodyList = type.bodyDeclarations();
		int lastEndPos = type.getName().getStartPosition()+type.getName().getLength();
		int currentStartPos = lastEndPos;
		int currentEndPos = 0;
		
		for(int i=0; i<bodyList.size();i++){
			BodyDeclaration bd = bodyList.get(i);
			
			boolean isStatic = (bd.getModifiers() & Modifier.STATIC) != 0;
    		
			MyVisitor visitor = new MyVisitor();
			bd.accept(visitor);
    		Map<String,Annotation> map = visitor.getAnnoMap();
    		
			currentStartPos = bd.getStartPosition();
			currentEndPos = bd.getStartPosition()+bd.getLength();
			if(i>0){
				BodyDeclaration prev = bodyList.get(i-1);
				lastEndPos = prev.getStartPosition()+prev.getLength();
			}
			//字段
			if(bd instanceof FieldDeclaration){
				FieldDeclaration fd = (FieldDeclaration)bd;
				
		        for (Object obj: fd.fragments()) {
		        	ModelField modelField = new ModelField();
		        	modelField.setAttributes(new LinkedHashMap());
		            VariableDeclarationFragment vd = (VariableDeclarationFragment)obj;
		            modelField.setName(vd.getName().getFullyQualifiedName());
		            modelField.setType(fd.getType().toString());
		            modelField.setDefaultValue(vd.getInitializer()==null?null:vd.getInitializer().toString());
		            this.setFieldComment(modelField, lastEndPos, currentEndPos);
		            
		            this.setFieldDbAonn(modelField, map);
		    		this.setAnnotationAttributes(modelField.getAttributes(), visitor.getAnnotations());
		    		
		    		modelField.setReference(this.getReference(modelField));
		    		
		    		if(!isStatic){
		    			fieldList.add(modelField);
		    		}else{
		    			staticFieldList.add(modelField);
		    		}
		        }
		    //方法
			}else if(bd instanceof MethodDeclaration){
				MethodDeclaration md = (MethodDeclaration)bd;
				
				Method method = new Method();
				method.setAttributes(new LinkedHashMap());
				method.setName(""+md.getName());
				method.setReturnType(""+md.getReturnType2());
				method.setModifiers(Modifier.toString(md.getModifiers()));
				method.setThrowz(this.getThrowz(md.thrownExceptions()));
				this.setMethodParameter(method, md);
				this.setMethodComment(method, lastEndPos, currentEndPos);
				
				this.setAnnotationAttributes(method.getAttributes(), visitor.getAnnotations());

				methodList.add(method);
			}
		}
	}
	
	private List<String> getThrowz(List thrownExceptions) {
		List<String> list = new ArrayList();
		for(Object t:thrownExceptions){
			list.add(""+t);
		}
		return list;
	}

	private void setFieldComment(ModelField modelField, int startPos, int endPos) {
//		System.out.println("{:\n"+this.sc.substring(startPos, endPos)+"\n:}");
		MyComment comment = this.queryComment2(startPos, endPos);
		if(comment != null){
			String text = comment.getValue();
			modelField.setLabel(text);
		}
		this.setCommentAttributes(comment, modelField.getAttributes());
	}
	
	private void setMethodComment(Method method, int startPos, int endPos) {
		MyComment comment = this.queryComment2(startPos, endPos);
		this.setMethodCommentInfo(method, comment);
		this.setCommentAttributes(comment, method.getAttributes());
	}

	private void setMethodParameter(Method method, MethodDeclaration md){
		List<Parameter> plist = new ArrayList<Parameter>();
		method.setParameters(plist);
		for(Object p : md.parameters()){
			SingleVariableDeclaration v = (SingleVariableDeclaration)p;
			String pv = ""+v.getType();
			Parameter param = new Parameter(""+v.getName(), pv);
			plist.add(param);
		}
	}

	private void setFieldDbAonn(ModelField modelField, Map<String,Annotation> map) {
		if(map.get("Id")!=null){
			modelField.setId(true);
		}
		if(map.get("GeneratedValue")!=null){
			modelField.setAuto(true);
		}
		if(map.get("Transient")!=null){
			modelField.setTransient(true);
		}
		if(map.get("Version")!=null){
			modelField.setVersion(true);	
		}
		NormalAnnotation columnAnno = (NormalAnnotation) map.get("Column");
		if(columnAnno!=null){
			Map<String, String> annoMap = this.getPropertyMap(columnAnno.values(), "name", "value");
			if(annoMap.get("length")!=null){
				String str = ""+annoMap.get("length");
				if(isInteger(str)){
					modelField.setLength(Integer.parseInt(str));
				}
			}
			if("false".equals(annoMap.get("nullable"))){
				modelField.setNullable(false);	
			}
			if("true".equals(annoMap.get("unique"))){
				modelField.setUnique(true);
			}
		}
	}
	
	private void setAnnotationAttributes(Map attributes, List<Annotation> list){
		for(Annotation anno:list){
			String key = ""+anno.getTypeName();
			Map<String,Object> properties = new LinkedHashMap<String,Object>();
			attributes.put(key, properties);
			if(anno instanceof SingleMemberAnnotation){
				SingleMemberAnnotation sa = (SingleMemberAnnotation)anno;
				properties.put("value", ""+sa.getValue());
			}else if(anno instanceof NormalAnnotation){
				NormalAnnotation na = (NormalAnnotation)anno;
				List<MemberValuePair> values = na.values();
				for(MemberValuePair vp:values){
					properties.put(""+vp.getName(), ""+vp.getValue());
				}
			}
		}
	}
	
	private void setCommentAttributes(MyComment comment, Map attributes){
		if(comment!=null && comment.getParams()!=null){
			List<MyDocParameter> params = comment.getParams();
			for(MyDocParameter param : params){
				String value = (param.getTag()!=null?param.getTag():"")+param.getValue();
				if(attributes.get(param.getType())==null){
					attributes.put(param.getType(), value);
				}else{
					attributes.put(param.getType(), attributes.get(param.getType())+","+value);
				}
			}			
		}
	}
	
	private Map<String,String> getPropertyMap(List list, String keyProperty, String valProperty){
		Map<String,String> result = new HashMap<String,String>();
		Map map = BeanUtil.getPropertyMap(list, keyProperty, valProperty);
		Set<Entry> entrySet = map.entrySet();
		for(Entry entry:entrySet){
			result.put(""+entry.getKey(), ""+entry.getValue());
		}
		return result;
	}
	
	private boolean isInteger(String str){
		return checkPattern("^\\d+$", str);
	}
	
	protected static boolean checkPattern(String regEx, String str) {
		if(str==null)return false;
		java.util.regex.Pattern p = java.util.regex.Pattern.compile(regEx);
		java.util.regex.Matcher m = p.matcher(str);
		return m.matches();
	}
	
	private String getFullName(CompilationUnit cunit, SimpleType type){
		if(type==null){
			return null;
		}
		PackageDeclaration jpackage = cunit.getPackage();
		String packageName = jpackage.getName().getFullyQualifiedName();
		
		String name = type.getName().getFullyQualifiedName();
		List<ImportDeclaration> importList = cunit.imports();
		String suffix = "."+name;
		for(ImportDeclaration imp:importList){
			String fname = imp.getName().getFullyQualifiedName();
			if(fname.endsWith(suffix)){
				return fname;
			}
		}
		return packageName+suffix;
	}
	char[] t;
	private char[] getCharBuf(File file, String charset){
		try {
			CharArrayWriter cwriter = new CharArrayWriter();
			Reader reader = new InputStreamReader(new FileInputStream(file), charset);
			char[] cbuf = new char[1024];
			int len = 0;
			while((len=reader.read(cbuf))!=-1){
				cwriter.write(cbuf, 0, len);
			}
			char[] r = cwriter.toCharArray();
			t=r;
			return r;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}

class MyVisitor extends ASTVisitor {
	private List<Annotation> annotations;
	
	public MyVisitor() {
		this.annotations = new ArrayList<Annotation>();
	}

	public MyVisitor(List<Annotation> annotations) {
		this.annotations = annotations;
	}

	public Map<String,Annotation> getAnnoMap(){
		Map<String,Annotation> map = new HashMap<String, Annotation>();
		for(Annotation anno:annotations){
			String typeName = ""+anno.getTypeName();
			map.put(typeName, anno);
		}
		return map;
	}
	
	public Annotation getAnnotation(String name){
		for(Annotation anno:annotations){
			String typeName = ""+anno.getTypeName();
			if(typeName.equals(name)){
				return anno;
			}
		}
		return null;
	}
	
	public List<Annotation> get(Integer startPosition, Integer endPosition){
		if(startPosition==null){
			startPosition = 0;
		}
		if(endPosition==null){
			endPosition = Integer.MAX_VALUE;
		}
		List<Annotation> list = new ArrayList<Annotation>();
		for(Annotation anno:annotations){
			if(anno.getStartPosition()>=startPosition && anno.getStartPosition()+anno.getLength()<=endPosition){
				list.add(anno);
			}
		}
		return list;
	}
	
	public List<Annotation> getAnnotations() {
		return annotations;
	}
	@Override
	public boolean visit(MarkerAnnotation node) {
		annotations.add(node);
		return true;  
	}
	@Override
	public boolean visit(NormalAnnotation node) {
		annotations.add(node);
		return true;  
	}
	@Override
	public boolean visit(SingleMemberAnnotation node) {
		annotations.add(node);
		return true;  
	}
}

